package com.yeskery.nut.cloud.loadbanlancer;

import com.yeskery.nut.application.ServerEventContext;
import com.yeskery.nut.bean.ApplicationContext;
import com.yeskery.nut.bean.aware.ApplicationContextAware;
import com.yeskery.nut.cloud.feign.RecordStats;
import com.yeskery.nut.cloud.registry.core.Instance;
import com.yeskery.nut.core.Environment;
import com.yeskery.nut.plugin.ServerEventPlugin;
import com.yeskery.nut.util.StringUtils;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.LongAdder;

/**
 * 计数熔断器
 * @author YESKERY
 * 2024/1/17
 */
public class CountBreaker implements Breaker, Recorder, ApplicationContextAware, ServerEventPlugin {

    /** 空服务实例id */
    private static final String EMPTY_INSTANCE_ID = "__EMPTY__";

    /** 最大失败次数Key */
    private static final String UNIT_MAX_FAIL_TIMES = "nut.cloud.breaker.counter.maxFailTimes";

    /** 单位毫秒数key */
    private static final String UNIT_MILLISECOND_KEY = "nut.cloud.breaker.counter.millisecond";

    /** 应用上下文 */
    private ApplicationContext applicationContext;

    /** 单位时间最大失败次数 */
    private int unitTimeMaxFailTimes = 3;

    /** 单位毫秒数时间 */
    private int unitMillisecond = 10000;

    /** 失败计数器map */
    private final Map<String, LongAdder> failCounterMap = new ConcurrentHashMap<>();

    /** 最后更新时间map */
    private final Map<String, Long> lastUpdateTimeMap = new ConcurrentHashMap<>();

    @Override
    public boolean shouldBreak(Instance instance) {
        String instanceId = instance == null ? EMPTY_INSTANCE_ID : instance.id();
        LongAdder longAdder = getInstanceAdder(instanceId);
        long currentTimeMillis = System.currentTimeMillis();
        Long lastTime = lastUpdateTimeMap.get(instanceId);
        lastUpdateTimeMap.put(instanceId, currentTimeMillis);
        if (lastTime == null) {
            return false;
        }
        if (currentTimeMillis - lastTime > unitMillisecond) {
            longAdder.reset();
            return false;
        }
        return longAdder.sum() >= unitTimeMaxFailTimes;
    }

    @Override
    public void failure(RecordStats recordStats, Instance instance, Throwable throwable) {
        String instanceId = instance == null ? EMPTY_INSTANCE_ID : instance.id();
        LongAdder longAdder = getInstanceAdder(instanceId);
        long currentTimeMillis = System.currentTimeMillis();
        Long lastTime = lastUpdateTimeMap.get(instanceId);
        lastUpdateTimeMap.put(instanceId, currentTimeMillis);
        if (lastTime != null && currentTimeMillis - lastTime > unitMillisecond) {
            longAdder.reset();
        } else {
            longAdder.increment();
        }
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) {
        this.applicationContext = applicationContext;
    }

    @Override
    public void afterStart(ServerEventContext serverEventContext) {
        this.beforeClose(serverEventContext);
        Environment environment = applicationContext.getBean(Environment.class);
        String unitTimeMaxFailTimes = environment.getEnvProperty(UNIT_MAX_FAIL_TIMES);
        if (!StringUtils.isEmpty(unitTimeMaxFailTimes)) {
            this.unitTimeMaxFailTimes = Integer.parseInt(unitTimeMaxFailTimes);
        }
        String unitMillisecond = environment.getEnvProperty(UNIT_MILLISECOND_KEY);
        if (!StringUtils.isEmpty(unitMillisecond)) {
            this.unitMillisecond = Integer.parseInt(unitMillisecond);
        }
    }

    @Override
    public void beforeClose(ServerEventContext serverEventContext) {
        failCounterMap.clear();
        lastUpdateTimeMap.clear();
    }

    /**
     * 获取实例叠加器
     * @param instanceId 实例对象id
     * @return 实例叠加器
     */
    private LongAdder getInstanceAdder(String instanceId) {
        LongAdder longAdder = failCounterMap.get(instanceId);
        if (longAdder != null) {
            return longAdder;
        }
        longAdder = failCounterMap.putIfAbsent(instanceId, new LongAdder());
        if (longAdder == null) {
            longAdder = failCounterMap.get(instanceId);
        }
        return longAdder;
    }
}
